🔷 При решении ноутбука используйте данный шаблон
✅ Можно добавлять новые ячейки любых типов
❌ Не нужно удалять текстовые ячейки c разметкой частей ноутбука и формулировками заданий
🔷 При оценивании задач учитывается код
✅ Задания, в которых необходим код, обычно помечаются фразами "Your code here"/"Ваш код" и аналогичными
❌ Ответы на вопросы без сопутствующего кода оцениваются в 0 баллов
❌ Наличе работоспособного кода в ноутбуке, если на сказано иного, обязательно
🔷 При оценивании задач учитываются выводы
✅ Задания, в которых необходимы выводы, обычно помечаются фразами Вывод"/"Ответ на вопрос"/"Ваш текст" и аналогичными
✅ Обычно выводы подразумевают под собой текстовый ответ (можно писать markdown, latex).
✅ Сопутствующие изображения, графики, таблички - приветствуются!
❌ При отсутствии выводов задание не засчитается на полный балл
В этом задании вы..:
**Примерное время выполнения (execution time/время выполнения, если нажать run all) всех ячеек ноутбука при правильной реализации: 60 минут **
Перед началом выполнения переведите ноутбук в Доверенный режим (Trusted) для корректного отображения изображений:
%config Completer.use_jedi = False
%load_ext autoreload
%autoreload 2
Сначала установим нужные нам версии библиотек. Мы гарантируем, что в данных версиях задание будет корректно отрабатывать.
После установки нужных версий, возможно, нужно перезагрузить среду (runtime), но скорее всего вам это не понадобится
На скачивание файла и установку понадобится не более 5 минут.
**Важно!**
Устанавливать нужные версии нужно каждый раз, когда создается новый рантайм. Например, если вы 2 часа подряд делаете это задание, то подготовить библиотеки достаточно 1 раз. Но если вы, например, начали в понедельник, затем закрыли/выключили ноутбук, то при продолжении в среду, вам нужно будет запустить рантайм заново и следовательно заново установить библиотеки.
**Важно!** Если вы предпочитаете делать практические задания на своем личном ноутбуке, то проверьте, что вы установили рабочее окружение в соответствии с гайдом.pdf)
**Важно!** В этом задании мы будем использовать полное виртуальное окружение, так как понадобятся библиотеки torch и tensorflow
Обратите внимание, что установка torch и tensorflow через pipможет сломать ваше окружение, особенно если вы используете GPU. Выполняйте их установку в соответствии с Вашей конфигурацией системы или в отдельном виртуальном окружении
# !!! Данный блок будет работать только в Google-Colab !!!
# ! gdown 19ZRLAdlNBI5OScrbxXzO3iaWJSkJlXeA
# ! pip install -r /content/requirements_2024_25_for_colab_full.txt
import catboost
assert(catboost.__version__ == '1.2.7')
Теперь можно приступать к выполнению задания! :)
В данной работе вам предстоит познакомится с методами машинного обучения без учителя — кластеризацией и алгоритмами снижения размерности.
Рекомендуется использовать Kaggle так как в нём корректно работают интерактивные визуализации.
Здесь перечислены основные функции и библиотеки, которые могут понадобиться Вам в процессе выполнения задания. Подключение других библиотек возможно, но нежелательно. Работа каких-либо других библиотек не гарантируется.
import os
import gdown
import scipy
import numpy as np
import tqdm.auto as tqdm
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from ipywidgets import interactive, fixed, interact_manual, IntSlider, FloatLogSlider, FloatSlider
import torch
from torchvision.datasets import CIFAR10
# Необходима преварительная установка tensorflow
from keras.applications.inception_v3 import InceptionV3, preprocess_input
import sklearn
from sklearn.decomposition import KernelPCA
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
# Библиотека umap-learn, а не umap
from umap import UMAP
from sklearn.manifold import TSNE, Isomap
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification, make_moons, make_blobs
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from warnings import simplefilter
from sklearn.exceptions import ConvergenceWarning
simplefilter("ignore", category=ConvergenceWarning)
2025-04-28 23:58:45.039736: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:467] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered WARNING: All log messages before absl::InitializeLog() is called are written to STDERR E0000 00:00:1745873925.125011 3715 cuda_dnn.cc:8579] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered E0000 00:00:1745873925.147431 3715 cuda_blas.cc:1407] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered W0000 00:00:1745873925.289386 3715 computation_placer.cc:177] computation placer already registered. Please check linkage and avoid linking the same target more than once. W0000 00:00:1745873925.289413 3715 computation_placer.cc:177] computation placer already registered. Please check linkage and avoid linking the same target more than once. W0000 00:00:1745873925.289415 3715 computation_placer.cc:177] computation placer already registered. Please check linkage and avoid linking the same target more than once. W0000 00:00:1745873925.289417 3715 computation_placer.cc:177] computation placer already registered. Please check linkage and avoid linking the same target more than once. 2025-04-28 23:58:45.306836: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations. To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
Определим вспомогательную функцию для отрисовки двумерных кластеризованных данных. При выполенении задания желательно пользоваться этой функцией для визуализации. При необходимости можете менять сигнатуру и поведение функции как вам удобно, оставляя стиль отрисовки в целом неизменным.
def plot_2d_data(data, labels, title='Исходные данные', cmap='tab20', ax=None):
'''
Отрисовка 2d scatter plot.
:param np.ndarray data: 2d массив точек
:param Union[list, np.ndarray] labels: список меток для каждой точки выборки
:param str title: Заголовок графика
:param str cmap: Цветовая палитра
:param ax Optional[matplotlib.axes.Axes]: Оси для отрисовки графика.
Если оси не заданы, то создаётся новая фигура и сразу же происходит её отрисовка
Иначе, график добавляется на существуюущие оси. Отрисовки фигуры не происходит
'''
n_clusters = len(np.unique(labels))
if ax is None:
fig, ax = plt.subplots(1, 1, figsize=(10, 5))
else:
fig = None
scatter = ax.scatter(
data[:, 0], data[:, 1], c=labels,
cmap=plt.get_cmap(cmap, n_clusters)
)
cbar = plt.colorbar(scatter, label='Номер кластера', ax=ax)
cbar.set_ticks(np.min(labels) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)
cbar.set_ticklabels(np.unique(labels))
ax.set_title(title)
ax.grid(True)
if fig is not None:
fig.tight_layout()
plt.show()
Также используйте написанные реализации из [Base] задания:
def silhouette_score(x, labels):
'''
:param np.ndarray x: Непустой двумерный массив векторов-признаков
:param np.ndarray labels: Непустой одномерный массив меток объектов
:return float: Коэффициент силуэта для выборки x с метками labels
'''
# Ваш код здесь:\(º □ º l|l)/
dist_matrix = sklearn.metrics.pairwise_distances(x)
num_clusters, count_clusters = np.unique(labels, return_counts=True)
len_labels = len(labels)
if len(num_clusters) <= 1:
return 0
masks = np.zeros((len_labels, len(num_clusters))).astype(bool)
sum_dists = np.zeros((len_labels, len(num_clusters)))
sizes_clusters = np.zeros(len_labels)
for i, cluster in enumerate(num_clusters):
masks[:, i] = labels == cluster
sum_dists[:, i] = np.sum(dist_matrix[:, labels == cluster], axis=1)
sizes_clusters[labels == cluster] = np.sum(labels == cluster)
one_elem_cluster = sizes_clusters == 1
s = sum_dists[masks]
s[one_elem_cluster] = 0
s[~one_elem_cluster] /= (sizes_clusters[~one_elem_cluster] - 1)
d = np.min(((sum_dists / count_clusters)
[~masks]).reshape(len_labels, -1), axis=1)
d[one_elem_cluster] = 0
ans = np.zeros(len_labels)
max_s = np.maximum(s, d)
np.divide(d - s, max_s, out=ans, where=(max_s != 0))
sil_score = np.mean(ans)
return sil_score
def bcubed_score(true_labels, predicted_labels):
'''
:param np.ndarray true_labels: Непустой одномерный массив меток объектов
:param np.ndarray predicted_labels: Непустой одномерный массив меток объектов
:return float: B-Cubed для объектов с истинными метками true_labels и предсказанными метками predicted_labels
'''
# Ваш код здесь:\(º □ º l|l)/
true_uniq, true_inv, true_count = np.unique(
true_labels, return_inverse=True, return_counts=True)
predicted_uniq, predicted_inv, predicted_count = np.unique(predicted_labels, return_inverse=True,
return_counts=True)
true_labels[true_labels == 0] = true_uniq[-1] + 1
predicted_labels[predicted_labels == 0] = predicted_uniq[-1] + 1
correctness = np.ones((len(true_labels), len(predicted_labels)))
correctness[(true_labels / true_labels[:, None]) != 1] = 0
correctness[(predicted_labels / predicted_labels[:, None]) != 1] = 0
precision = np.mean(np.sum(correctness, axis=1) /
predicted_count[predicted_inv])
recall = np.mean(np.sum(correctness, axis=1) / true_count[true_inv])
score = 2 * precision * recall / (precision + recall)
return score
При выполнении задания запрещено:
При оформлении задания обратите внимание на форматирование кода и на оформление графиков:
Графики должны быть с одной стороны понятными и информативными, а с другой стороны красивыми. Вот несколько пунктов, которые помогут удовлетворить этим требования:
matplotlib_inline.backend_inline.set_matplotlib_formats('pdf', 'svg'). Если изображения в векторном формате приводят к слишком большому размеру Jupyter Notebook можете использовать растровые изображения с высоким dpi. Напирмер, можно установить глобальный dpi в matplotlib: matplotlib.rcParams['figure.dpi'] = 300title)log, symlog по необходимости[xy]ticks, [xy]ticklabels вручную. Подписи тиков на осях не должны сливаться как на одной оси, так и между нимиplt.style.use('seaborn-colorblind')
# Или
plt.style.use('tableau-colorblind10')
# Затем, при отрисовке графиков не используйте параметр cmap
Синтетические данные имеют достаточно простую структуру, поэтому методы снижения размерности позволяют получать хорошее низкоразмерное представление с достаточно выраженными кластерами. Однако, реальные данные могут быть устроены существенно сложнее. Посмотрим как поведут себя методы снижения размерности на датасете с картинками CIFAR10.
Загрузим датасет. Будем использовать только часть обучающей выборки, чтобы ускорить вычисления на высокоразмерных данных.
cifar10_test_dataset = CIFAR10('./cifar10', train=False, download=True)
cifar10_train_dataset = CIFAR10('./cifar10', train=True, download=False)
cifar10_labels_test = np.array(cifar10_test_dataset.targets)
cifar10_labels_train = np.array(cifar10_train_dataset.targets)
cifar10_images_test = cifar10_test_dataset.data
cifar10_images_train = cifar10_train_dataset.data
cifar10_images_train, _, cifar10_labels_train, _ = train_test_split(
cifar10_images_train, cifar10_labels_train,
train_size=cifar10_images_test.shape[0], stratify=cifar10_labels_train, random_state=6886
)
cifar10_data_test = (cifar10_images_test.astype(np.float32) / 255.0).reshape([cifar10_images_test.shape[0], -1])
cifar10_data_train = (cifar10_images_train.astype(np.float32) / 255.0).reshape([cifar10_images_train.shape[0], -1])
Files already downloaded and verified
np.unique(cifar10_labels_train)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Отобразим данные в проекции на две случайные оси. Для удобства воспользуемся здесь ещё одним вариантом динамического контента в jupyter notebook — при наведении на точку на графике будем отображать исходную картинку.
def plot_interactive(lowd_data, images, labels, names, n_dots=1000, image_scale=1.0):
with matplotlib.rc_context(rc={
'font.size': image_scale * matplotlib.rcParams['font.size'],
'xtick.major.size': image_scale * matplotlib.rcParams['xtick.major.size'],
'xtick.minor.size': image_scale * matplotlib.rcParams['xtick.minor.size'],
'ytick.major.size': image_scale * matplotlib.rcParams['ytick.major.size'],
'ytick.minor.size': image_scale * matplotlib.rcParams['ytick.minor.size'],
'axes.linewidth': image_scale * matplotlib.rcParams['axes.linewidth'],
'grid.linewidth': image_scale * matplotlib.rcParams['grid.linewidth'],
'patch.linewidth': image_scale * matplotlib.rcParams['patch.linewidth'],
'xtick.major.width': image_scale * matplotlib.rcParams['xtick.major.width'],
'xtick.minor.width': image_scale * matplotlib.rcParams['xtick.minor.width'],
'ytick.major.width': image_scale * matplotlib.rcParams['ytick.major.width'],
'ytick.minor.width': image_scale * matplotlib.rcParams['ytick.minor.width'],
'lines.markeredgewidth': image_scale * matplotlib.rcParams['lines.markeredgewidth'],
}):
fig, ax = plt.subplots(1, 1, figsize=(image_scale * 10, image_scale * 5))
fig.set_dpi(300)
ax.grid(True)
n_clusters = len(np.unique(labels))
scatter = plt.scatter(
lowd_data[:n_dots, 0], lowd_data[:n_dots, 1], s=image_scale * 10,
c=labels[:n_dots], cmap=plt.get_cmap('tab20', n_clusters), edgecolors='none'
)
cbar = plt.colorbar(scatter, ax=ax, label='Название кластера')
cbar.set_ticks(np.min(labels[:n_dots]) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)
cbar.set_ticklabels(names)
offset_image = OffsetImage(images[0], zoom=image_scale * 2.0)
ann_bbox = AnnotationBbox(
offset_image, (0,0), xybox=(image_scale * 50., image_scale * 50.), xycoords='data',
boxcoords="offset points", pad=0.3, arrowprops=dict(
arrowstyle='->, head_length={0:.2f}, head_width={1:.2f}'.format(
image_scale * 0.4, image_scale * 0.2
)
)
)
ax.add_artist(ann_bbox)
ax.set_title('Распределение данных CIFAR10 в проекции на 2 случайные оси')
ann_bbox.set_visible(False)
def image_hover(event):
if scatter.contains(event)[0]:
ind, *_ = scatter.contains(event)[1]["ind"]
w, h = fig.get_size_inches() * fig.dpi
ws = (event.x > w / 2.) * -1 + (event.x <= w / 2.)
hs = (event.y > h / 2.) * -1 + (event.y <= h / 2.)
ann_bbox.xybox = (image_scale * 50.0 * ws, image_scale * 50.0 * hs)
ann_bbox.set_visible(True)
ann_bbox.xy =(lowd_data[ind, 0], lowd_data[ind, 1])
offset_image.set_data(images[ind])
else:
ann_bbox.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect('motion_notify_event', image_hover)
plt.show()
%matplotlib ipympl
matplotlib.rcParams['figure.dpi'] = 300
# Для работы в Google Colab нужно выполнить специфичную магию
# Обычно, она не срабатывает с первого раза, поэтому может потребоваться
# несколько раз выполнить ячейку и несколько раз попробовать нарисовать график
try:
from google.colab import output
output.enable_custom_widget_manager()
except:
pass
# Если картинка окажется слишком маленькой/большой, то поменяйте image_scale на подходящее значение
plot_interactive(
cifar10_data_train[:, [17, 64]], cifar10_images_train, cifar10_labels_train,
cifar10_test_dataset.classes, n_dots=2000, image_scale=0.35
)
Вернёмся в статичный режим отрисовки изображений:
%matplotlib inline
matplotlib.rcParams['figure.dpi'] = 300
</font>
Воспользуйтесь алгоритмами снижения размерности TSNE, UMAP, Isomap, KernelPCA для визуализации картинок.
Постройте визуализацию низкоразмерного представления, полученного с помощью этих моделей — изобразите четыре графика в одной строке. Во второй строке отобразите результат применения обученных моделей на тестовой выборке. Если для данного алгоритма невозможно сделать предсказания на тестовой выборке — оставьте соответствующий график пустым. Обозначьте разными цветами разные классы объектов. Для повышения производительности можете отобразить только часть выборки на графике ($1000\text{-}2000$ объектов).
**Замечание:** обратите внимание, что все алгоритмы снижения размерности также требуют правильного масштабирования признаков, для корректной работы и интерпретируемых результатов.
# Ваш код здесь:\(º □ º l|l)/
scaler = StandardScaler()
cifar10_data_train_scaled = scaler.fit_transform(cifar10_data_train)
cifar10_data_test_scaled = scaler.transform(cifar10_data_test)
tsne = TSNE(n_components=2)
umap = UMAP(n_components=2)
isomap = Isomap(n_components=2)
kernelpca = KernelPCA(n_components=2)
tsne_train = tsne.fit_transform(cifar10_data_train_scaled)
umap_train = umap.fit_transform(cifar10_data_train_scaled)
isomap_train = isomap.fit_transform(cifar10_data_train_scaled)
kernelpca_train = kernelpca.fit_transform(cifar10_data_train_scaled)
# tsne_test = tsne.transform(cifar10_data_test_scaled)
umap_test = umap.transform(cifar10_data_test_scaled)
isomap_test = isomap.transform(cifar10_data_test_scaled)
kernelpca_test = kernelpca.transform(cifar10_data_test_scaled)
def make_skatter(title, x, ax, c, classes, n_dot = 1500):
if x is not None:
scatter = ax.scatter(x[:n_dot, 0], x[:n_dot, 1], c=c[:n_dot], cmap = 'Set1', s = 10)
n_clusters = len(np.unique(c[:n_dot]))
cbar = plt.colorbar(scatter, ax=ax)
cbar.set_ticks(np.min(c[:n_dot]) + (np.arange(n_clusters) + 0.5) * (n_clusters - 1) / n_clusters)
cbar.set_ticklabels(classes)
ax.set_title(title)
ax.grid(True)
fig, ax = plt.subplots(2, 4, figsize=(20, 8))
make_skatter(title = 'TSNE (Train)', x = tsne_train, ax = ax[0][0], c = cifar10_labels_train, classes=cifar10_train_dataset.classes)
make_skatter(title = 'UMAP (Train)', x = umap_train, ax = ax[0][1], c = cifar10_labels_train, classes=cifar10_train_dataset.classes)
make_skatter(title = 'Isomap (Train)', x = isomap_train, ax = ax[0][2], c = cifar10_labels_train, classes=cifar10_train_dataset.classes)
make_skatter(title = 'KernelPCA (Train)', x = kernelpca_train, ax = ax[0][3], c = cifar10_labels_train, classes=cifar10_train_dataset.classes)
make_skatter(title = 'TSNE (Test)', x = None, ax = ax[1][0], c = cifar10_labels_test, classes=cifar10_test_dataset.classes)
make_skatter(title = 'UMAP (Test)', x = umap_test, ax = ax[1][1], c = cifar10_labels_test, classes=cifar10_test_dataset.classes)
make_skatter(title = 'Isomap (Test)', x = isomap_test, ax = ax[1][2], c = cifar10_labels_test, classes=cifar10_test_dataset.classes)
make_skatter(title = 'KernelPCA (Test)', x = kernelpca_test, ax = ax[1][3], c = cifar10_labels_test, classes=cifar10_test_dataset.classes)
Опишите увиденное. Почему алгоритмы могли отработать не так, как вы ожидали?
**Ваш ответ здесь:** (o・・)ノ”(ノ<、): Данные не очень хорошо выделились на кластеры, графики, которые получились, не позволяют выделить четкие кластеры.
Это может быть из-за $\textbf{большой размерности данных или шума в них}$ и их нельзя спроецировать на двухразмерную плоскость.
Методы снижения размерности, как и другие метрические методы испытывают трудности при работе с данными высокой размерности. Напишите как минимум две причины, почему.
**Ваш ответ здесь:** (o・・)ノ”(ノ<、):
Один из способов решения этих проблем — перейти в другое, более репрезентативное пространство признаков, где объекты будут расположены в многообразии, которое легче представить в двумерном пространстве. Чтобы выполнить такое преобразование воспользуемся типичным подходом Transfer Learning — предобученными нейронными сетями. С помощью глубокой сети обученной на другом наборе изображений (ImageNet) мы перейдём в новое векторное пространство и затем применим методы снижения размерности.
Так как локальный подсчёт эмбеддингов изображений может занять много времени, Вы можете попробовать скачать их c помощью gdown:
gdown.download(id='16UgWo1Emt9ar1O4h2Xxed0ZpJZ0OG5V-', output='cifar10_deep_features.npy')
Downloading... From (original): https://drive.google.com/uc?id=16UgWo1Emt9ar1O4h2Xxed0ZpJZ0OG5V- From (redirected): https://drive.google.com/uc?id=16UgWo1Emt9ar1O4h2Xxed0ZpJZ0OG5V-&confirm=t&uuid=f161d18a-3a43-4274-9dfb-c3d459f4c4c8 To: /home/alina/ML/cifar10_deep_features.npy 100%|██████████| 164M/164M [27:48<00:00, 98.2kB/s]
'cifar10_deep_features.npy'
FEATURES_PATH = './cifar10_deep_features.npy'
if not os.path.exists(FEATURES_PATH):
deep_cnn = InceptionV3(weights='imagenet', include_top=False, input_shape=(139, 139, 3))
cifar10_tensors_test = torch.nn.functional.interpolate(torch.tensor(
cifar10_images_test.transpose(0, 3, 1, 2)
), size=139).numpy().transpose(0, 2, 3, 1).astype(np.float32)
cifar10_tensors_train = torch.nn.functional.interpolate(torch.tensor(
cifar10_images_train.transpose(0, 3, 1, 2)
), size=139).numpy().transpose(0, 2, 3, 1).astype(np.float32)
cifar10_deep_features_test = deep_cnn.predict(
preprocess_input(cifar10_tensors_test)
).mean(axis=(1, 2)).reshape([cifar10_tensors_test.shape[0], -1])
cifar10_deep_features_train = deep_cnn.predict(
preprocess_input(cifar10_tensors_train)
).mean(axis=(1, 2)).reshape([cifar10_tensors_train.shape[0], -1])
np.save(FEATURES_PATH, [cifar10_deep_features_test, cifar10_deep_features_train])
else:
cifar10_deep_features_test, cifar10_deep_features_train = np.load(FEATURES_PATH, allow_pickle=True)
# Ваш код здесь:\(º □ º l|l)/\
scaler_deep = StandardScaler()
cifar10_deep_features_train_scaled = scaler_deep.fit_transform(cifar10_deep_features_train)
cifar10_deep_features_test_scaled = scaler_deep.transform(cifar10_deep_features_test)
tsne_deep = TSNE(n_components=2)
umap_deep = UMAP(n_components=2)
isomap_deep = Isomap(n_components=2)
kernelpca_deep = KernelPCA(n_components=2)
tsne_train_deep = tsne_deep.fit_transform(cifar10_deep_features_train_scaled)
umap_train_deep = umap_deep.fit_transform(cifar10_deep_features_train_scaled)
isomap_train_deep = isomap_deep.fit_transform(cifar10_deep_features_train_scaled)
kernelpca_train_deep = kernelpca_deep.fit_transform(cifar10_deep_features_train_scaled)
# tsne_test = tsne.transform(cifar10_data_test_scaled)
umap_test_deep = umap_deep.transform(cifar10_deep_features_test_scaled)
isomap_test_deep = isomap_deep.transform(cifar10_deep_features_test_scaled)
kernelpca_test_deep = kernelpca_deep.transform(cifar10_deep_features_test_scaled)
fig, ax = plt.subplots(2, 4, figsize=(20, 8))
make_skatter(title = 'TSNE (Train)', x = tsne_train_deep, ax = ax[0][0], c = cifar10_labels_train, classes=cifar10_train_dataset.classes)
make_skatter(title = 'UMAP (Train)', x = umap_train_deep, ax = ax[0][1], c = cifar10_labels_train, classes=cifar10_train_dataset.classes)
make_skatter(title = 'Isomap (Train)', x = isomap_train_deep, ax = ax[0][2], c = cifar10_labels_train, classes=cifar10_train_dataset.classes)
make_skatter(title = 'KernelPCA (Train)', x = kernelpca_train_deep, ax = ax[0][3], c = cifar10_labels_train, classes=cifar10_train_dataset.classes)
make_skatter(title = 'TSNE (Test)', x = None, ax = ax[1][0], c = cifar10_labels_test, classes=cifar10_test_dataset.classes)
make_skatter(title = 'UMAP (Test)', x = umap_test_deep, ax = ax[1][1], c = cifar10_labels_test, classes=cifar10_test_dataset.classes)
make_skatter(title = 'Isomap (Test)', x = isomap_test_deep, ax = ax[1][2], c = cifar10_labels_test, classes=cifar10_test_dataset.classes)
make_skatter(title = 'KernelPCA (Test)', x = kernelpca_test_deep, ax = ax[1][3], c = cifar10_labels_test, classes=cifar10_test_dataset.classes)
**Ваш ответ здесь:** (o・・)ノ”(ノ<、):
Далее, для визуализации кластеризации используйте один из методов снижения размерности на ваш выбор и то векторное представление, которое лучше всего себя проявило (исходное или полученное с помощью глубокой сети). Кластеризацию обучайте также на наиболее подходящем высокоразмерном векторном представлении.
Изобразите выборку CIFAR10 с помощью выбранного алгоритма снижения размерности.
**Совет** Изобразите результат с помощью plot_interactive, чтобы изучить особенности кластеризации в соответствии с исходными изображениями. Если вы нашли интересные особенности — напишите про это.
%matplotlib ipympl
matplotlib.rcParams['figure.dpi'] = 300
# Для работы в Google Colab нужно выполнить специфичную магию
# Обычно, она не срабатывает с первого раза, поэтому может потребоваться
# несколько раз выполнить ячейку и несколько раз попробовать нарисовать график
try:
from google.colab import output
output.enable_custom_widget_manager()
except:
pass
# Ваш код здесь:\(º □ º l|l)/
plot_interactive(umap_train_deep, cifar10_images_train, cifar10_labels_train,
cifar10_test_dataset.classes, n_dots=2000, image_scale=0.35
)
Вернёмся в статичный режим отрисовки изображений:
%matplotlib inline
matplotlib.rcParams['figure.dpi'] = 300
Теперь, когда мы можем визуализировать кластеризацию, можно сравнить алгоритмы из первой части на естественных данных.
Подберите параметры KMeans, DBSCAN, AgglomerativeClustering используя силуэт и B-Cubed. Визуализируйте получившиеся кластеризации также, как и в задании 1.с.4 в ноутбуке [Base] Clusterizartion. Для ускорения перебора можете производить его на небольшой доле от всех объектов ($1000\text{-}2000$ объектов).
Замечание: Алгоритмы кластеризации нужно применять к исходному векторному представлению. Снижение размерности используется только для визуализации.
n_objects = 2000
# Ваш код здесь:\(º □ º l|l)/
def plot_clustering_results(data, true_labels, algorithm, param_grid):
fig = plt.figure(figsize=(15, 8))
fig.suptitle(f'Алгоритм {algorithm}', fontsize = 30)
# 1. Подготовка данных для графиков
if algorithm == 'DBSCAN':
eps_values = param_grid['eps']
min_samples_values = param_grid['min_samples']
sil_scores = np.zeros((len(eps_values), len(min_samples_values)))
bcubed_scores = np.zeros_like(sil_scores)
for i, eps in enumerate(eps_values):
for j, min_samples in enumerate(min_samples_values):
model = DBSCAN(eps=eps, min_samples=min_samples)
pred_labels = model.fit_predict(data)
if len(np.unique(pred_labels)) > 1:
sil_scores[i,j] = silhouette_score(data, pred_labels)
bcubed_scores[i,j] = bcubed_score(true_labels, pred_labels)
else:
sil_scores[i,j] = -1
bcubed_scores[i,j] = 0
else:
n_clusters_values = param_grid['n_clusters']
sil_scores = []
bcubed_scores = []
for n_clusters in n_clusters_values:
if algorithm == 'KMeans':
model = KMeans(n_clusters=n_clusters, random_state=6417)
else:
model = AgglomerativeClustering(n_clusters=n_clusters, linkage='ward')
pred_labels = model.fit_predict(data)
sil_scores.append(silhouette_score(data, pred_labels))
bcubed_scores.append(bcubed_score(true_labels, pred_labels))
# 2. Создание графиков
if algorithm == 'DBSCAN':
# Heatmap для Silhouette
ax1 = fig.add_subplot(221)
im1 = ax1.imshow(sil_scores, cmap='viridis', origin='lower',
extent=[min_samples_values[0], min_samples_values[-1],
eps_values[0], eps_values[-1]])
ax1.set_title('Silhouette')
ax1.set_xlabel('min_samples')
ax1.set_ylabel('eps')
fig.colorbar(im1, ax=ax1)
# Heatmap для B-Cubed
ax2 = fig.add_subplot(222)
im2 = ax2.imshow(bcubed_scores, cmap='viridis', origin='lower',
extent=[min_samples_values[0], min_samples_values[-1],
eps_values[0], eps_values[-1]])
ax2.set_title('B-Cubed')
ax2.set_xlabel('min_samples')
ax2.set_ylabel('eps')
fig.colorbar(im2, ax=ax2)
else:
# Графики для KMeans/Agglomerative
ax1 = fig.add_subplot(221)
ax1.plot(n_clusters_values, sil_scores, 'bo-')
ax1.set_title('Silhouette ')
ax1.set_xlabel('Number of clusters')
ax1.grid(True)
ax2 = fig.add_subplot(222)
ax2.plot(n_clusters_values, bcubed_scores, 'ro-')
ax2.set_title('B-Cubed')
ax2.set_xlabel('Number of clusters')
ax2.grid(True)
# 3. Визуализация лучших кластеризаций
if algorithm == 'DBSCAN':
best_sil_idx = np.unravel_index(np.argmax(sil_scores), sil_scores.shape)
best_bcubed_idx = np.unravel_index(np.argmax(bcubed_scores), bcubed_scores.shape)
best_sil_model = DBSCAN(eps=eps_values[best_sil_idx[0]],
min_samples=min_samples_values[best_sil_idx[1]])
best_bcubed_model = DBSCAN(eps=eps_values[best_bcubed_idx[0]],
min_samples=min_samples_values[best_bcubed_idx[1]])
else:
best_sil_idx = np.argmax(sil_scores)
best_bcubed_idx = np.argmax(bcubed_scores)
if algorithm == 'KMeans':
best_sil_model = KMeans(n_clusters=n_clusters_values[best_sil_idx],
random_state=6417)
best_bcubed_model = KMeans(n_clusters=n_clusters_values[best_bcubed_idx],
random_state=6417)
else:
best_sil_model = AgglomerativeClustering(n_clusters=n_clusters_values[best_sil_idx],
linkage='ward')
best_bcubed_model = AgglomerativeClustering(n_clusters=n_clusters_values[best_bcubed_idx],
linkage='ward')
umap = UMAP(n_components=2)
data_umap = umap.fit_transform(data)
# Визуализация лучших моделей
ax3 = fig.add_subplot(223)
ax3.set_title(algorithm)
best_sil_labels = best_sil_model.fit_predict(data)
plot_2d_data(data_umap, best_sil_labels,
title=f'Best Silhouette clustering\nParams: {get_params(best_sil_model)}',
ax=ax3)
ax4 = fig.add_subplot(224)
ax4.set_title(algorithm)
best_bcubed_labels = best_bcubed_model.fit_predict(data)
plot_2d_data(data_umap, best_bcubed_labels,
title=f'Best B-Cubed clustering\nParams: {get_params(best_bcubed_model)}',
ax=ax4)
plt.tight_layout()
plt.show()
def get_params(model):
if isinstance(model, DBSCAN):
return f"eps={model.eps}, min_samples={model.min_samples}"
elif isinstance(model, KMeans):
return f"n_clusters={model.n_clusters}"
else:
return f"n_clusters={model.n_clusters}, linkage={model.linkage}"
data = cifar10_deep_features_train_scaled[:n_objects]
k_params = {'n_clusters': range(2, 11)}
dbscan_params = {'eps': np.linspace(0.05, 2.0, 20),
'min_samples': range(4, 20, 1) }
agg_params = {'n_clusters': range(2, 11)}
plot_clustering_results(data, cifar10_labels_train[:n_objects], 'KMeans', k_params)
plot_clustering_results(data, cifar10_labels_train[:n_objects], 'DBSCAN', dbscan_params)
plot_clustering_results(data, cifar10_labels_train[:n_objects], 'AgglomerativeClustering', agg_params)
/home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10) /home/alina/ML/ml/lib/python3.12/site-packages/sklearn/cluster/_kmeans.py:1416: FutureWarning: The default value of `n_init` will change from 10 to 'auto' in 1.4. Set the value of `n_init` explicitly to suppress the warning super()._check_params_vs_input(X, default_n_init=10)
**Ваш ответ здесь:** (o・・)ノ”(ノ<、):
Интересный способ визуализации Иерархической кластеризации — построение дендрограммы. Такой способ визуализации позволяет анализировать, как именно связаны между собой объекты, подбирать оптимальное число кластеров, а также определять, какие классы отделяются "хорошо" от других классов, а какие классы перемешаны в одном кластере.
def plot_dendrogram(model, labels, classes, ax):
n_classes = len(classes)
n_samples = len(model.labels_)
n_u_connections = model.children_.shape[0]
colors = plt.get_cmap('tab20', n_classes).colors
bin_counts = np.zeros([n_u_connections, n_classes])
for i, merge in enumerate(model.children_):
current_bin_count = np.zeros(n_classes)
for child_idx in merge:
if child_idx < n_samples:
current_bin_count[labels[child_idx]] += 1
else:
current_bin_count += bin_counts[child_idx - n_samples]
bin_counts[i] = current_bin_count
linkage_matrix = np.column_stack(
[model.children_, model.distances_, np.sum(bin_counts, axis=1)]
).astype(float)
def leaf_label_func(idx):
if idx < len(labels):
return None
else:
ratio = 100 * np.max(bin_counts[idx - n_samples]) / np.sum(bin_counts[idx - n_samples])
if ratio < 100:
return '{0:.0f}%'.format(ratio)
else:
return None
def link_color_func(idx):
mode_class = np.argmax(bin_counts[idx - n_samples])
return matplotlib.colors.to_hex(colors[mode_class], keep_alpha=True)
scipy.cluster.hierarchy.dendrogram(
linkage_matrix, ax=ax, link_color_func=link_color_func, leaf_label_func=leaf_label_func,
orientation='right', truncate_mode="level", p=9
)
for idx, class_name in enumerate(classes):
ax.plot([], [], c=matplotlib.colors.to_hex(colors[idx], keep_alpha=True), label=class_name)
ax.legend()
# Удалим накладывающиеся метки
threshold = 55
prev_position = -(threshold + 1)
y_labels = ax.get_yaxis().get_ticklabels()
for label in y_labels:
if label.get_text() == '':
continue
_, position = label.get_position()
if position - prev_position < threshold:
label.set_text('')
else:
prev_position = position
ax.get_yaxis().set_ticklabels(y_labels)
ax.set_xlabel('Расстояние между кластерами')
ax.set_ylabel('Доля объектов наибольшего класса в данном кластере')
ax.set_title('Дендрограмма Иерархической Кластеризации')
n_objects = 2000
model = AgglomerativeClustering(
n_clusters=None, distance_threshold=0.0, compute_distances=True, compute_full_tree=True
)
model = model.fit(cifar10_deep_features_train[:n_objects])
fig, ax = plt.subplots(1, 1, figsize=(12, 12))
plot_dendrogram(
model,
labels=cifar10_labels_train[:n_objects],
classes=cifar10_train_dataset.classes,
ax=ax
)
fig.tight_layout()
plt.show()
Проанализируйте получившуюся дендрограмму. Напишите свои наблюдения ниже.
**Ваш ответ здесь:** (o・・)ノ”(ノ<、):
Наиболее крупные объединения происходят при больших расстояниях, что говорит о ярко выраженной схожести кластеров. По дендрограмме можно понять, что данные можно разделить на относительно 8-10 устойчивых групп.
Можно заметить, что изображения класса "ship" формируют отдельный кластер из множества объектов на больших расстояниях. Эти объекты объединились на больших расстояниях, что говорит о высокой схожести внутри класса и существенных различиях по сравнению с другими классами. Интересно получается, что классы 'airplane' 'horse', 'automobile' и 'deer' могут попасть под один кластер 'ship', если взять большое расстояние между кластерами.